<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">{{title}}</div>
    <script>
      // 1.基本结构
      const Vue = {
        // 扩展性
        createRenderer({ querySelector, insert }) {
          // 返回渲染器
          return {
            createApp(options) {
              return {
                mount(selector) {
                  // 1. 找到宿主元素
                  const parent = querySelector(selector);
                  // 2. 渲染页面
                  if (!options.render) {
                    //  2.1 处理template：编译
                    options.render = this.compile(parent.innerHTML);
                  }
                  // setup和其他选项
                  const proxy = options.setup();
                  //  2.2 用户直接编写render
                  this.update = effect(() => {
                    const el = options.render.call(proxy);
                    parent.innerHTML = "";
                    // 3. 追加到宿主
                    insert(el, parent);
                  });
                  this.update();
                },
                compile(template) {
                  // 返回一个render函数
                  // parse -> ast
                  // generate -> ast => render
                  return function render() {
                    const h3 = document.createElement("h3");
                    h3.textContent = this.title;
                    return h3;
                  };
                },
              };
            },
          };
        },
        createApp(options) {
          // 创建一个web平台特有的渲染器
          const renderer = Vue.createRenderer({
            querySelector(sel) {
              return document.querySelector(sel);
            },
            insert(el, parent) {
              parent.appendChild(el);
            },
          });
          return renderer.createApp(options);
        },
      };
    </script>
    <script>
      // 能够拦截用户对代理对象的访问
      // 从而在值发生变化的时候做出响应式
      function reactive(obj) {
        // vue2 Object.defineProperty
        return new Proxy(obj, {
          get(target, key) {
            console.log("get key", key);
            // 建立依赖关系
            track(target, key);
            return target[key];
          },
          set(target, key, val) {
            console.log("set key", key);
            target[key] = val;
            // 获取依赖关系, 触发更新
            trigger(target, key);
            // 通知更新
            // app.update();
          },
        });
      }

      // 建立映射关系：依赖dep - 组件更新函数
      // vue2： 1组件有watcher
      // vue3： 创建map结构 { target: { key: [update1, update2] }}

      // 调用effect，首先执行fn
      const effectStack = [];
      function effect(fn) {
        const eff = function () {
          try {
            effectStack.push(eff);
            fn();
          } finally {
            effectStack.pop();
          }
        };
        // 立即调用一次
        eff();
        return eff;
      }

      const targetMap = {};
      // 建立target，key和effectStack中存储的副作用函数之间的关系
      function track(target, key) {
        const effect = effectStack[effectStack.length - 1];
        // 判断target为key的对象是否存在
        let map = targetMap[target];
        if (!map) {
          map = targetMap[target] = {};
        }
        let deps = map[key];
        if (!deps) {
          deps = map[key] = [];
        }
        // 映射关系建立
        if (deps.indexOf(effect)) {
          deps.push(effect);
        }
      }

      function trigger(target, key) {
        const map = targetMap[target];
        if (map) {
          const deps = map[key];
          if (deps) {
            deps.forEach((dep) => dep());
          }
        }
      }

      // const obj = reactive({ foo: "foo" });
      // effect(() => {
      //   console.log(obj.foo);
      // });
      // obj.foo = "foo~~";

      const app = Vue.createApp({
        setup() {
          const state = reactive({
            title: "vue3, hello",
          });
          setTimeout(() => {
            state.title = "hello, vue3";
          }, 2000);
          return state;
        },
      });
      app.mount("#app");
    </script>
  </body>
</html>
